a tool for shared writing and social publishing
1import { z } from "zod";
2import type { Fact } from "src/replicache";
3import type { Attribute } from "src/replicache/attributes";
4import { makeRoute } from "../lib";
5import type { Env } from "./route";
6import { scanIndexLocal } from "src/replicache/utils";
7import * as base64 from "base64-js";
8import { YJSFragmentToString } from "src/utils/yjsFragmentToString";
9import { applyUpdate, Doc } from "yjs";
10
11export const getFactsFromHomeLeaflets = makeRoute({
12 route: "getFactsFromHomeLeaflets",
13 input: z.object({
14 tokens: z.array(z.string()),
15 }),
16 handler: async ({ tokens }, { supabase }: Pick<Env, "supabase">) => {
17 let all_facts = await supabase.rpc("get_facts_for_roots", {
18 max_depth: 3,
19 roots: tokens,
20 });
21
22 if (all_facts.data) {
23 let titles = {} as { [key: string]: string };
24
25 let facts = all_facts.data.reduce(
26 (acc, fact) => {
27 if (!acc[fact.root_id]) acc[fact.root_id] = [];
28 acc[fact.root_id].push(fact as unknown as Fact<Attribute>);
29 return acc;
30 },
31 {} as { [key: string]: Fact<Attribute>[] },
32 );
33 for (let token of tokens) {
34 let scan = scanIndexLocal(facts[token]);
35 let [root] = scan.eav(token, "root/page");
36 let rootEntity = root?.data.value || token;
37
38 // Check page type to determine which blocks to look up
39 let [pageType] = scan.eav(rootEntity, "page/type");
40 let isCanvas = pageType?.data.value === "canvas";
41
42 // Get blocks and sort by position
43 let rawBlocks = isCanvas
44 ? scan.eav(rootEntity, "canvas/block").sort((a, b) => {
45 if (a.data.position.y === b.data.position.y)
46 return a.data.position.x - b.data.position.x;
47 return a.data.position.y - b.data.position.y;
48 })
49 : scan.eav(rootEntity, "card/block").sort((a, b) => {
50 if (a.data.position === b.data.position)
51 return a.id > b.id ? 1 : -1;
52 return a.data.position > b.data.position ? 1 : -1;
53 });
54
55 // Map to get type and filter for text/heading
56 let blocks = rawBlocks
57 .map((b) => {
58 let type = scan.eav(b.data.value, "block/type")[0];
59 if (
60 !type ||
61 (type.data.value !== "text" && type.data.value !== "heading")
62 )
63 return null;
64 return b.data;
65 })
66 .filter((b): b is NonNullable<typeof b> => b !== null);
67
68 let title = blocks[0];
69
70 if (!title) titles[token] = "Untitled";
71 else {
72 let [content] = scan.eav(title.value, "block/text");
73 if (!content) titles[token] = "Untitled";
74 else {
75 let doc = new Doc();
76 const update = base64.toByteArray(content.data.value);
77 applyUpdate(doc, update);
78 let nodes = doc.getXmlElement("prosemirror").toArray();
79 let stringValue = YJSFragmentToString(nodes[0]);
80 titles[token] = stringValue;
81 }
82 }
83 }
84 return {
85 result: { facts, titles },
86 };
87 }
88
89 return { result: {} };
90 },
91});